package glossos.tree;

import glossos.search.Searcher;
import glossos.tree.factory.NodeFactory;
import glossos.tree.factory.SimpleNodeFactory;
import glossos.tree.snapshot.InMemoryInsertionSnapshot;
import glossos.tree.snapshot.InsertionSnapshot;

import java.io.IOException;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Observable;
import java.util.Observer;

import org.apache.log4j.Logger;

import com.google.common.collect.Lists;

public class FrequencyTree implements Searcher, Observer {
    private static Logger indexLogger = Logger.getLogger("glossos.index");
    private Integer height;
    private InsertionSnapshot snapshot;
    private Integer minimumSentenceSize;
    private NodeFactory nodeFactory;

    public FrequencyTree(int height, NodeFactory nodeFactory) {
        this(new InMemoryInsertionSnapshot(height, new SimpleNodeFactory()), nodeFactory);
    }

    public FrequencyTree(InsertionSnapshot snapshot, NodeFactory nodeFactory) {
        this.height = snapshot.height();
        this.snapshot = snapshot;
        this.minimumSentenceSize = 1;
        this.nodeFactory = nodeFactory;
    }

    public void start() {
        snapshot.insert(nodeFactory.create());
    }

    public void setMinimumSentenceSize(Integer minimumSentenceSize) {
        this.minimumSentenceSize = minimumSentenceSize;
    }

    @SuppressWarnings("unchecked")
    public void update(Observable o, Object arg) {
        if (arg != null)
            update((List<String>) arg);
        else {
            indexLogger.info("Frequency tree constructed.");
        }
    }

    public void update(List<String> wordList) {
        if (wordList.size() >= minimumSentenceSize) {
            for (String word : wordList)
                append(word.toLowerCase());
            snapshot.reset();
        }
    }

    private void append(String word) {
        for (int i = 0; snapshot.canAppendUnderLevel(i); i++)
            snapshot.appendWordUnderLevel(word, i);
        snapshot.commit();
    }

    public String mostFrequentSequence(Integer size) {
        Node node = snapshot.mostFrequentAtLevel(size);
        return node != null ? node.sequence() : "";
    }

    public Integer frequency(String sequence) {
        Node node = lowestNodeForSequence(sequence.split("\\s"));
        return node.getFrequency();
    }

    private Node lowestNodeForSequence(String... sequence) {
        Node node = snapshot.root();

        for (String word : sequence) {
            if (!node.hasChildWithWord(word))
                return nodeFactory.create();
            node = node.childWithWord(word);
        }
        return node;
    }

    // for testing purposes only
    public void logResults(Writer writer) {
        try {
            for (int i = 1; i <= height; i++) {
                String sequence = mostFrequentSequence(i);
                Integer frequency = frequency(sequence);

                String result = "Most frequent sentence of size " + i + ": " + sequence + " (" + frequency + " occurrences)\n";
                writer.write(result);
            }
            writer.close();
        } catch (IOException e) {
            indexLogger.error(e.getMessage());
        }
    }

    public List<String> sequencesMatching(String pattern[], int start, int size) {
        /* this method can be optimized. Instead of retrieving all results and cutting it down,
           * the traversal of the tree should stop when the size was reached.
           */
        List<String> complete = sequencesMatching(pattern);
        return complete.size() >= size
            ? complete.subList(start, start + size)
            : complete;
    }

    public List<String> sequencesMatching(String[] pattern) {
        Node root = snapshot.root();
        indexLogger.debug("Root node: " + root);
        Collection<Node> nodesForPattern = root.nodesForPattern(pattern, 0);
        indexLogger.debug("nodes for pattern " + Arrays.asList(pattern) + ": " + nodesForPattern);
        List<Node> sortedNodes = new ArrayList<Node>(nodesForPattern);

        Collections.sort(sortedNodes, new Comparator<Node>() {
            public int compare(Node o1, Node o2) {
                return o1.getFrequency().compareTo(o2.getFrequency()) * -1;
            }
        });

        return Lists.transform(sortedNodes, new NodeToSequenceFunction(null));
    }
}
